Skip to content

Add managed llama.cpp lifecycle and settings on macOS arm64 - #60

Merged
JaredP94 merged 9 commits into
mainfrom
feat/llamacpp-engine
Nov 7, 2026
Merged

JaredP94 merged 9 commits into
mainfrom
feat/llamacpp-engine

Conversation

@JaredP94

@JaredP94 JaredP94 commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Description

Add llama.cpp as a PAIR-managed engine on macOS arm64: install, start/stop, runtime status, server port, typed inference-only launch arguments and a PAIR-controlled child environment (no user environment assignments). This is PR 1 of 4, adapting selected lifecycle concepts from NVIDIA/Personal-AI-Router@6d2d67f, with upstream author credit on each commit. NVIDIA#153 informs identity-bound installation/removal; NVIDIA#186 remains deferred.

The official b12171 archive is pinned to SHA-256 870ef967970fa18bbd31460bc5f7929bbb5f091359d8dc0f58e20133aa4b5fcd. The macOS build is not notarized, like PAIR itself; the engine lists and documentation say so. It does not serve requests through PAIR yet.

Scope

  • Bounded tar.gz staging with strip-one-component semantics, contained internal links, required server/library layout (Metal resources are embedded), checksum verification and quarantine rejection. /usr/bin/codesign --verify --strict --all-architectures checks every Mach-O file, detected by thin/fat magic bytes rather than filename or executable bit: server, adjacent dylibs, extensionless code and all universal slices. Required layout, link containment, quarantine and signature integrity are revalidated on install, the same-pin already-installed path and before every start. Never clear quarantine or re-sign.
  • Closed b12171 launch allowlist (llamacpp_policy.go, documented aliases in LLAMACPP.md): context, batch and microbatch sizes; threads and batch threads; GPU layers; flash attention; K/V cache types; parallel slots; prediction length; temperature, top-k, top-p and min-p; named built-in chat templates; reasoning format, mode, effort and budget; load mode; continuous batching, KV offload, metrics and slot switches. Numeric ranges and enum choices are typed and bounded. Canonical option names are --ctx-size, --batch-size, --ubatch-size, --threads, --threads-batch, --gpu-layers, --flash-attn, --cache-type-k, --cache-type-v, --parallel, --predict, --temp, --top-k, --top-p, --min-p, --chat-template, --reasoning-format, --reasoning, --reasoning-effort, --reasoning-budget, --load-mode, --cont-batching/--no-cont-batching, --kv-offload/--no-kv-offload, --metrics, --slots/--no-slots. --load-mode accepts auto/none/mmap/mlock/mmap+mlock/dio; standalone --mlock, --mmap and --no-mmap are rejected. Unknown or repeated options (including aliases/opposing switches), positional arguments, --, non-ASCII, all environment assignments, paths, URLs, model downloads and arbitrary template programs are rejected. Equals syntax is lowered to separate argv values for the pinned parser.
  • No parent environment is inherited: ambient LLAMA_ARG_*, cache/download configuration and dynamic-loader overrides cannot leak into the child. PAIR supplies only manifest-owned listener/cache settings, reuse-port disabled, offline mode, fixed system PATH and isolated child HOME/XDG paths. Before every spawn, any file, symlink or inspection failure at /etc/llama.cpp/config.ini or {install_dir}/.pair-config/llama.cpp/config.ini refuses launch; the real user configuration is not searched and ambient files are never modified. Invalid saved settings are reported, not silently discarded or applied.
  • TestLlamaAllowlistMatchesPinnedRelease checks every allowed name/alias against testdata/llamacpp-b12171-options.txt, snapshotted from actual common_arg(...) option definitions in b12171 common/arg.cpp, not incidental option-name mentions. It also covers accepted load modes and rejects stale standalone memory switches. Regenerate the snapshot when changing the release pin.
  • Atomic first install and owned-directory exchange on update, identity-bound claims, failed-validation rollback, uninstall preserving the sibling LLAMA_CACHE directory. Updating requires stopping the engine first; clients never uninstall it before an update.
  • Default 127.0.0.1:8082, collision/reservation checks, /props JSON role=router readiness, existing validated LAN-bind path, audited protected listener flags/environment and underscore aliases. Complete audit: services/nvpair-engine-manager/LLAMACPP.md.
  • Proxy-free engine settings with existing revision/recovery protocol (proxyPort=0), minimal desktop/TUI lifecycle list support and original repository-native icon. No load profiles.
  • Engine-manager 1.5.0 (minor relative to v0.9.0's 1.4.0), broker 2.6.0, TUI 1.4.0.
  • Deferred PR 2: proxy worker, discovery and routing. PR 3: Go model hub/download/delete. PR 4: remaining desktop/TUI and release integration. No new worker or unified-proxy plumbing. No Windows process-ownership files changed.

Validation

Current-head packaged live check (bfae1272ab48c84fcdbb91830025bb71fdf4f12b, 2026-11-07; macOS 27.2 arm64, remote Python 3.9.6):

  • Rebuilt in a fresh detached /tmp/pair-work/live60 worktree using npm ci --no-audit --no-fund and npm run -s build:mac:arm64 (both exit 0); no source edits or dependency symlinks. NVPAIR-Setup-0.9.0-arm64.zip: 194752822 bytes, SHA-256 e9f737022b9e4798fc8396e651b7f81c3eb9d33d7b5fea09798f41ffd186bacf, verified locally and remotely with shasum -a 256.
  • Captured the live broker argv, quit PAIR, confirmed its processes exited, then backed up the published 0.9.0 application and app data with ditto into ~/pair-live60/backup/. diff -rq returned 0 for both copies (app-data comparison excluded Singleton* and logs; macOS diff emitted framework-symlink directory-loop warnings). The backup was retained and never modified. Installed the built app and ran only its packaged broker under a Python 3.9-compatible isolated driver over JSON-RPC stdio.
Check Result Live evidence
a. Availability PASS engine:get-installed: llamacpp, installed=false, capabilities.install=true.
b. PAIR install PASS Progress: downloading 1–100%, installing, done; installed=true. Recursive read-only xattr -lr produced no attributes; codesign --verify --strict --all-architectures exited 0. spctl --assess --type execute --verbose=4 exited 3: llama-server: rejected (not notarized; no bypass attempted).
c. PAIR start PASS running=true, healthy=true; /props HTTP 200 and role=router; lsof showed only IPv4 127.0.0.1:8082. Environment names only: LLAMA_ARG_API_PREFIX, LLAMA_ARG_HOST, LLAMA_ARG_OFFLINE, LLAMA_ARG_PORT, LLAMA_ARG_REUSE_PORT, LLAMA_CACHE; no HF_* names.
d. Settings PASS --ctx-size 4096 preview valid; --host 0.0.0.0, --tools all, -hf org/repo, --mlock each rejected with option is not in the llama.cpp inference allowlist. Applied context at revision 2, observed a new healthy PID and literal argv, then restored the exact original settings (serverPort=8082, proxyPort=0, empty launch text) at revision 3 with another healthy restart and /props 200/router.
e. Stop PASS PAIR returned running=false, healthy=false; lsof found no listener on 8082.
f. No routing FAIL / incomplete /v1/models on 11434 and 1234 each returned HTTP 503 with exact body {"error":"model inventory unavailable"}; neither supplied a successful inventory, so absence of llama.cpp routing cannot be certified there. Port 1236 returned HTTP 200 with no llama.cpp entries. Stopped further feature testing at the unexpected response and proceeded with cleanup/restoration; this is not evidence that llama.cpp caused those inventory failures.
g. PAIR uninstall PASS (cache retention not exercised) PAIR returned installed=false and removed $APP_DATA/engine-bin/llamacpp. $APP_DATA/engine-bin/llamacpp-models was absent before and after uninstall; no cache was created to manufacture a retention test.
  • Cleanly shut down the test broker (exit 0), confirmed no PAIR/nvpair/llama-server processes remained, replaced the application and app-data folder from the retained backups with ditto, and verified both restored copies with diff -rq (exit 0, same exclusions/warnings). Relaunched the published app: version 0.9.0, broker plus all 12 workers running. Deleted ~/pair-live60/incoming; kept ~/pair-live60/backup/. Removed the temporary local worktree.
  • Evidence: local /tmp/pair-review/live-llamacpp/evidence-2.jsonl (JSON lines, including install progress, requests/responses, read-only checks and restoration). No model was downloaded, no other engine was installed/uninstalled, no downloaded archive executable was run directly, and no quarantine/signature/Gatekeeper setting was changed. The earlier hashlib.file_digest harness problem was avoided with Python 3.9-compatible scripts and shasum.
  • This supersedes the prior transfer-only live-check attempt and its live-status references below. Checks a–e and g ran on this package; f remains incomplete. Packaged update/rollback, inference, existing-cache retention and hosted macOS CI are not certified by this run.

Previously recorded validation below is retained as historical evidence; it was not rerun for this live-check attempt and does not certify the current package or complete hosted macOS coverage.

Local macOS arm64, fixture-only; no downloaded release executable run and no real engine installed:

  • In each touched Go module (nvpair-engine-manager, nvpair-ui-broker, nvpair-tui): gofmt -l, go vet ./..., GOOS=linux go vet ./..., GOOS=windows go vet ./..., go test -count=1 ./... — all pass (format output empty).
  • Own worktree desktop: npm ci --no-audit --no-fund; npm run -s typecheck, npm run -s lint (zero warnings), npm run -s test:unit (95 files / 944 tests), npm run -s dead-code:check, npm run -s service-contracts:write, npm run -s service-contracts:check — pass; generated API documentation unchanged.
  • node scripts/spdx-headers.mjs: 0 missing; git diff --check: clean. pgrep -lf 'fake-engine|llama-server': no survivors.
  • New fixture coverage includes TestArchiveContainment, TestArchiveLimitsAndCancellation, TestArchiveChecksumMismatch, TestArchiveQuarantinePreservedAndRejected, TestArchiveQuarantinedSourceRejected, TestArchiveSignatureIntegrity, TestArchiveCodesignUnavailable, TestArchiveInstallRollback, TestArchiveExecutorInstallUpdateRollback, TestArchiveAtomicUpdate, TestArchiveUninstallPreservesModelCacheAndRequiresClaim, TestArchiveManifestValidation, TestLlamaProtectedLaunchInputs, TestLlamaReadinessIdentity, TestLlamaPortCollision, plus broker proxy-free settings/recovery and desktop/TUI lifecycle/update tests. Regression tests observed failing before fixes and passing afterward.
  • Cross-process tests were not run locally. Previously recorded CI run 42251407665, commit 7bbe62b3: Desktop checks, SPDX headers, Go modules (ubuntu-latest), and Cross-process tests (ubuntu-latest) all pass. The Go modules job includes the full engine-manager race suite and repeated downloader shutdown regressions.
  • Downloaded cross-process-ubuntu-latest from that recorded run into fresh directory /tmp/pair-llamacpp-final-artifact.6LBRgZ; python3 -I /tmp/pair-review/scripts/noskip.py /tmp/pair-llamacpp-final-artifact.6LBRgZ/cross-process.jsonl reports 123 pass, zero skips/failures.
  • Both macOS jobs, Go modules (macos-15) and Cross-process tests (macos-15), never acquired a runner: empty runner name, zero steps, then cancelled after 15 minutes, in both runs. Consequently that run had 4/6 jobs pass; it was not fully green, and hosted macOS coverage remains unverified. No workflow or runner workaround was added.
  • Initial Linux module CI exposed two ownership fixtures placed in platform-independent files. Their success-path directory claims rely on macOS birth-time identities; Linux intentionally uses change-time identities. Moved these fixtures unchanged into the Darwin suite, retaining portable extraction/checksum/manifest tests on Linux. Native archive tests/full suite and all three vet targets pass after this test-only correction; that Linux CI run passes.

Risk

Other platforms are explicitly unsupported for this engine. Checksum and existing ad-hoc signature integrity do not establish publisher trust or notarization. Installation fails closed on quarantine, a broken signature or unavailable codesign. No quarantine attributes are removed, and no signatures are changed.

Packaged macOS arm64 live install/update/rollback and start/stop smoke checks remain the maintainer's pre-merge task. The current-head attempt stopped at transfer verification before engine testing; no live llama.cpp run is claimed. The two hosted macOS jobs also remain a pre-merge gate because no runner was acquired. Independent automated review could not complete (reviewer timeouts); local source/diff review and the checks above completed. This PR is not an inference or release certification.

Checklist

  • I have read the Contributing Guidelines.
  • Every commit is signed off (git commit -s), certifying the Developer Certificate of Origin.
  • New or existing tests cover the change.
  • Relevant documentation is updated.
  • I checked the diff, changed filenames, and commit messages for credentials, private data, internal URLs, internal issue identifiers, and generated artifacts.
  • I recorded the validation commands and results above.
  • I bumped any affected component in services/versions.json, and described user-visible changes above so they reach the release notes.

JaredP94 and others added 5 commits November 6, 2026 22:49
Add a bounded tar.gz installer with contained library links, required-file verification, unchanged quarantine attributes and strict existing-signature checks. Stage identity-bound claims with the artifact and atomically exchange owned macOS installations, leaving failed installs and updates unchanged. Preserve sibling model caches on uninstall. This prepares the lifecycle-only port of NVIDIA/Personal-AI-Router@6d2d67f without its proxy plumbing.

Co-authored-by: Terve <ntervalon@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Adapt the lifecycle-only manifest from NVIDIA/Personal-AI-Router@6d2d67f to the fork, pin b12171 and require its complete server library layout. Protect every audited listener control and port-changing preset, including vendor underscore spellings and inherited environment. Require the /props router identity for readiness and health. Document the unnotarized release, port 8082, retained cache and deferred routing/model integration.

Co-authored-by: Terve <ntervalon@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Expose llama.cpp installation, runtime state and protected server launch settings without a proxy or model routing. Keep settings revision recovery and collision checks in Go, display the unnotarized build notice, and preserve atomic archive updates from desktop and TUI clients.

Adapt the lifecycle-only scope from NVIDIA/Personal-AI-Router@6d2d67f and its follow-ups NVIDIA#153 and NVIDIA#186; the proxy and model hub remain deferred. Reject quarantined source archives without altering their attributes and extend fixture coverage.

Co-authored-by: Terve <ntervalon@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
The supported archive installer relies on macOS birth-time identities surviving staging-marker writes and directory exchange. Linux intentionally uses change time, so successful directory-claim fixtures cannot describe an install on that unsupported platform. Keep these rollback and cache-preserving uninstall tests in the native Darwin suite while retaining portable extraction, checksum and manifest coverage everywhere. CI exposed both misplaced fixtures; the native suite and cross-platform compilation now pass.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Replace the llama.cpp launch denylist with a closed policy audited against b12171. Validate saved overrides and normalize documented aliases and equals syntax without admitting host tools, remote model sources, filesystem paths or readiness changes.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94
JaredP94 force-pushed the feat/llamacpp-engine branch from 7bbe62b to 3dd35cf Compare November 6, 2026 20:58
Launch with only PAIR-owned environment values and child-only HOME/XDG configuration paths. Refuse the unavoidable system config and any config in the controlled user search path before spawn; false reuse-port environment values alone cannot undo a config setting.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
…nd start

Review follow-up. Only llama-server's signature was checked, so a
damaged adjacent library such as libllama-server-impl.dylib passed, and
the same-pin already-installed path skipped the quarantine and signature
checks entirely. Every installed Mach-O file, identified by its thin or
fat magic bytes rather than name or execute bit, is now checked with
codesign --verify --strict --all-architectures (argv, fixed system tool,
fail closed). Required files and link containment are revalidated. The
checks run on install, on the same-pin return and before every start
(under the engine operation lock). Nothing re-signs or clears quarantine.

Tests use /usr/lib/dyld, which is always present, signed and universal,
as the library fixture.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Review follow-up. The allowlist accepted --mlock, --mmap and --no-mmap,
which llama-server b12171 does not define; it rejects unknown arguments,
so a saved setting would pass PAIR's validation and then stop the engine
from starting. Replace them with the typed --load-mode/-lm option and
its six b12171 values. Add a snapshot of b12171's option names and a test
that every allowlisted name exists in it, so a stale name fails a test
rather than a launch.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
…tions

Review follow-up. The snapshot of b12171 option names was extracted from
every quoted string starting with a dash, which picked up the value -1
and missed names containing dots such as --fim-qwen-1.5b-default. It now
lists exactly the names in each common_arg(...) definition's leading
brace groups.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94

JaredP94 commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

Live-check note before merge (retirement-plan, macOS 27.2 arm64, package built from bfae127): install, start, settings and stop all passed. PAIR installed b12171 with no quarantine, codesign --verify --strict passed, and spctl rejected it, as expected for an unnotarized build. The server came up healthy on 127.0.0.1:8082 only, /props returned role router, rejected options were refused, a settings change was applied and restored exactly, and stop released the port.

Step f ("nothing routes to llama.cpp") was recorded as incomplete because the Ollama and LM Studio proxies answered 503 {"error":"model inventory unavailable"}. That is the published 0.9.0's normal response when those engines are stopped: it was re-checked on the same machine running 0.9.0 and returns the same 503. Routing to llama.cpp is excluded structurally: this PR changes no proxy, scheduler, scanner or workload code, llama.cpp reports list_models: false, and its proxy port is zero. The oMLX proxy's model list contained no llama.cpp entries.

Uninstall removed the install directory. No model-cache directory existed, so cache retention on uninstall is covered by unit tests only. The machine was restored to the published 0.9.0 with its data.

@JaredP94
JaredP94 merged commit ab5d5ef into main Nov 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant